﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;
namespace Tesla.Scene.Shape {
    /// <summary>
    /// Single-holed torus shape.
    /// </summary>
    public class Torus : Mesh {

        /// <summary>
        /// Initializes a new instance of the <see cref="Torus"/> class.
        /// </summary>
        /// <param name="name">The object name.</param>
        /// <param name="circleSamples">The number of circle samples.</param>
        /// <param name="radialSamples">The number of radial samples.</param>
        /// <param name="tubeRadius">The tube radius.</param>
        /// <param name="centerRadius">The center radius.</param>
        public Torus(String name, int circleSamples, int radialSamples, float tubeRadius, float centerRadius)
            : base(name) {
                SetData(circleSamples, radialSamples, tubeRadius, centerRadius, false);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Torus"/> class.
        /// </summary>
        /// <param name="name">The object name.</param>
        /// <param name="circleSamples">The number of circle samples.</param>
        /// <param name="radialSamples">The number of radial samples.</param>
        /// <param name="tubeRadius">The tube radius.</param>
        /// <param name="centerRadius">The center radius.</param>
        /// <param name="insideOut">True if the shape should be turned inside out or not.</param>
        public Torus(String name, int circleSamples, int radialSamples, float tubeRadius, float centerRadius, bool insideOut)
            : base(name) {
            SetData(circleSamples, radialSamples, tubeRadius, centerRadius, insideOut);
        }

        /// <summary>
        /// Recompute's the torus' geometry based on the supplied constraints.
        /// </summary>
        /// <param name="circleSamples">The number of circle samples.</param>
        /// <param name="radialSamples">The number of radial samples.</param>
        /// <param name="tubeRadius">The tube radius.</param>
        /// <param name="centerRadius">The center radius.</param>
        public void SetData(int circleSamples, int radialSamples, float tubeRadius, float centerRadius) {
            SetData(circleSamples, radialSamples, tubeRadius, centerRadius, false);
        }

        /// <summary>
        /// Recompute's the torus' geometry based on the supplied constraints.
        /// </summary>
        /// <param name="circleSamples">The number of circle samples.</param>
        /// <param name="radialSamples">The number of radial samples.</param>
        /// <param name="tubeRadius">The tube radius.</param>
        /// <param name="centerRadius">The center radius.</param>
        /// <param name="insideOut">True if the shape should be turned inside out or not.</param>
        public void SetData(int circleSamples, int radialSamples, float tubeRadius, float centerRadius, bool insideOut) {
            MeshData.UseDynamicIndexBuffer = false;
            MeshData.UseDynamicVertexBuffer = false;
            MeshData.UseIndexedPrimitives = true;
            GenerateGeometry(circleSamples, radialSamples, tubeRadius, centerRadius, insideOut);
            GenerateIndices(circleSamples, radialSamples, tubeRadius, centerRadius, insideOut);
            MeshData.Reconstruct();
            UpdateModelBound();
        }

        private void GenerateGeometry(int circleSamples, int radialSamples, float tubeRadius, float centerRadius, bool insideOut) {
            int vertCount = (circleSamples + 1) * (radialSamples + 1);
            DataBuffer<Vector3> positions = new DataBuffer<Vector3>(vertCount);
            DataBuffer<Vector3> normals = new DataBuffer<Vector3>(vertCount);
            DataBuffer<Vector2> texCoords = new DataBuffer<Vector2>(vertCount);

            //Generate geometry
            float inverseCircleSamples = 1.0f / (float) circleSamples;
            float inverseRadialSamples = 1.0f / (float) radialSamples;
            int i = 0;
            for(int circleCount = 0; circleCount < circleSamples; circleCount++) {
                //Compute center point on torus circle at specified angle
                float circleFraction = circleCount * inverseCircleSamples;
                float theta = MathHelper.TwoPi * circleFraction;
                float cosTheta = (float) System.Math.Cos(theta);
                float sinTheta = (float) System.Math.Sin(theta);
                Vector3 radialAxis = new Vector3(cosTheta, sinTheta, 0);
                Vector3 torusMiddle;
                Vector3.Multiply(ref radialAxis, centerRadius, out torusMiddle);

                //Compute slice vertices with duplication at end point
                int iSave = i;
                for(int radialCount = 0; radialCount < radialSamples; radialCount++) {
                    float radialFraction = radialCount * inverseRadialSamples;
                    float phi = MathHelper.TwoPi * radialFraction;
                    float cosPhi = (float) System.Math.Cos(phi);
                    float sinPhi = (float) System.Math.Sin(phi);

                    Vector3 normal;
                    Vector3.Multiply(ref radialAxis, cosPhi, out normal);
                    normal.Z += sinPhi;
                    normal.Normalize();
                    if(insideOut) {
                        Vector3 tempNormal = new Vector3(-normal.X, -normal.Y, -normal.Z);
                        normals.Set(tempNormal);
                    } else {
                        normals.Set(normal);
                    }
                    Vector3 position;
                    Vector3.Multiply(ref normal, tubeRadius, out position);
                    Vector3.Add(ref position, ref torusMiddle, out position);
                    positions.Set(position);
                    Vector2 tex = new Vector2(radialFraction, circleFraction);
                    texCoords.Set(tex);
                    i++;
                }
                Vector3 pos = positions.Get(iSave);
                positions.Set(pos);
                Vector3 norm = normals.Get(iSave);
                normals.Set(norm);

                texCoords.Set(new Vector2(1.0f, circleFraction));
                i++;
            }

            //Duplicate cylinder ends to form a torus
            for(int iR = 0; iR <= radialSamples; iR++, i++) {
                Vector3 pos = positions.Get(iR);
                positions.Set(pos, i);
                Vector3 norm = normals.Get(iR);
                normals.Set(norm, i);
                Vector2 tex = texCoords.Get(iR);
                texCoords.Set(tex, i);
                texCoords.Set(new Vector2((i * 2) + 1, 1.0f));
            }

            MeshData.UpdateVertexData<Vector3>(VertexSemantic.Position, positions);
            MeshData.UpdateVertexData<Vector3>(VertexSemantic.Normal, normals);
            MeshData.UpdateVertexData<Vector2>(VertexSemantic.TextureCoordinate, texCoords);
        }

        private void GenerateIndices(int circleSamples, int radialSamples, float tubeRadius, float centerRadius, bool insideOut) {
            int vertCount = (circleSamples + 1) * (radialSamples + 1);
            int triCount = 2 * circleSamples * radialSamples;
            DataBuffer<int> indices = new DataBuffer<int>(3 * triCount);
            int i;
            int connectionStart = 0;
            int index = 0;
            for(int circleCount = 0; circleCount < circleSamples; circleCount++) {
                int i0 = connectionStart;
                int i1 = i0 + 1;
                connectionStart += radialSamples + 1;
                int i2 = connectionStart;
                int i3 = i2 + 1;
                for(i = 0; i < radialSamples; i++, index += 6) {
                    if(insideOut) {
                        indices.Set(i0++);
                        indices.Set(i2);
                        indices.Set(i1);
                        indices.Set(i1++);
                        indices.Set(i2++);
                        indices.Set(i3++);
                    } else {
                        indices.Set(i0++);
                        indices.Set(i1);
                        indices.Set(i2);
                        indices.Set(i1++);
                        indices.Set(i3++);
                        indices.Set(i2++);
                    }
                }
            }
            MeshData.UpdateIndexData<int>(indices);
        }
    }
}
